/*
 * Copyright (c) 2017, 2019, APT Group, School of Computer Science,
 * The University of Manchester. All rights reserved.
 * Copyright (c) 2007, 2012, Oracle and/or its affiliates. All rights reserved.
 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
 *
 * This code is free software; you can redistribute it and/or modify it
 * under the terms of the GNU General Public License version 2 only, as
 * published by the Free Software Foundation.
 *
 * This code is distributed in the hope that it will be useful, but WITHOUT
 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
 * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
 * version 2 for more details (a copy is included in the LICENSE file that
 * accompanied this code).
 *
 * You should have received a copy of the GNU General Public License version
 * 2 along with this work; if not, write to the Free Software Foundation,
 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
 */
package com.sun.max.vm.code;

import static com.sun.max.vm.MaxineVM.*;
import static com.sun.max.vm.VMOptions.*;

import java.util.*;

import com.sun.max.annotate.*;
import com.sun.max.unsafe.*;
import com.sun.max.vm.*;
import com.sun.max.vm.actor.holder.*;
import com.sun.max.vm.actor.member.*;
import com.sun.max.vm.compiler.target.*;
import com.sun.max.vm.compiler.target.TargetBundleLayout.*;
import com.sun.max.vm.heap.*;
import com.sun.max.vm.heap.debug.*;
import com.sun.max.vm.layout.*;
import com.sun.max.vm.reference.*;
import com.sun.max.vm.runtime.*;
import com.sun.max.vm.tele.*;
import com.sun.max.vm.type.*;

/**
 * Target machine code cache management.
 *
 * All generated code is position independent as a whole, but target methods may contain direct call references between
 * each other and these must be within 32-bit offsets! Therefore all code regions must be within 32-bit offsets from
 * each other. A concrete implementation of this class must enforce this invariant.
 */
public abstract class CodeManager {

    /**
     * VM option for specifying the amount of memory to be reserved for the runtime baseline code region cache.
     * Experiments have shown that a vast amount of baseline code is generated, so this region is rather large until
     * code eviction logic is properly in place.
     */
    public static final VMSizeOption runtimeBaselineCodeRegionSize =
        register(new VMSizeOption("-XX:ReservedBaselineCodeCacheSize=", Size.M.times(256),
            "Memory allocated for runtime code region cache."), MaxineVM.Phase.PRISTINE);

    /**
     * VM option for specifying the amount of memory to be reserved for the runtime opt code region cache.
     * Experiments have shown that very little such code is generated, so this region is rather small.
     */
    public static final VMSizeOption runtimeOptCodeRegionSize =
        register(new VMSizeOption("-XX:ReservedOptCodeCacheSize=", Size.M.times(16),
            "Memory allocated for runtime code region cache."), MaxineVM.Phase.PRISTINE);

    private int nAllocations = 0;

    private int lastSurvivorSize;
    private int largestSurvivorSize = 0;

    protected void recordSurvivorSize(int survivorSize) {
        lastSurvivorSize = survivorSize;
        if (survivorSize > largestSurvivorSize) {
            largestSurvivorSize = survivorSize;
        }
    }

    /**
     * Performs code cache validation.
     * This has a return type of {@code boolean} so that it can be used as the condition in an
     * assertion. That is, code cache validation is predicated on assertions being enabled.
     */
    private boolean validateCodeCache() {
        CodeCacheValidation.instance.submit();
        return true;
    }

    /**
     * This option can be used to force baseline code cache contention every N method allocations.
     * It monitors the amount of surviving methods/bytes after each contention and dumps those, including the largest.
     */
    public static int CodeCacheContentionFrequency;

    static {
        VMOptions.addFieldOption("-XX:", "CodeCacheContentionFrequency", CodeManager.class,
            "Enforce baseline code cache contention every N method allocations.", MaxineVM.Phase.STARTING);
    }

    /**
     * Categorization of how long a method is destined to stay around.
     */
    public enum Lifespan {
        /**
         * Class initializers etc.
         */
        ONE_SHOT,
        /**
         * Methods that will likely be removed after some time (e.g., compiled by a baseline compiler).
         */
        SHORT,
        /**
         * Methods that stay (e.g., compiled by an optimizing compiler).
         */
        LONG;
    }

    /**
     * The baseline code region contains machine code generated by the baseline compiler.
     */
    @INSPECTED
    protected static final SemiSpaceCodeRegion runtimeBaselineCodeRegion = new SemiSpaceCodeRegion("Code-Runtime-Baseline");

    /**
     * The opt code region contains machine code generated by the optimising compiler as well as adapters and trampolines.
     */
    @INSPECTED
    protected static final CodeRegion runtimeOptCodeRegion = new CodeRegion("Code-Runtime-Opt");

    /**
     * Get the runtime baseline code region.
     * @return the runtime baseline code region
     */
    public SemiSpaceCodeRegion getRuntimeBaselineCodeRegion() {
        return runtimeBaselineCodeRegion;
    }

    /**
     * Get the runtime opt code region.
     * @return the runtime baseline code region
     */
    public CodeRegion getRuntimeOptCodeRegion() {
        return runtimeOptCodeRegion;
    }

    /**
     * Initialize this code manager.
     */
    void initialize() {
    }

    private static int BOOT_TO_BASELINE_INITIAL_SIZE = 10;

    /**
     * Records all direct call links from the boot code region to the baseline code region.
     */
    private static TargetMethod[] bootToBaseline = new TargetMethod[BOOT_TO_BASELINE_INITIAL_SIZE];

    private static int nBootToBaseline = 0;

    public static int bootToBaselineSize() {
        return nBootToBaseline;
    }

    public static synchronized void recordBootToBaselineCaller(final TargetMethod tm) {
        if (CodeEviction.logging()) {
            CodeEviction.codeEvictionLogger.logBootToBaseline(tm);
        }
        if (nBootToBaseline == bootToBaseline.length) {
            bootToBaseline = Arrays.copyOf(bootToBaseline, bootToBaseline.length * 2);
        }
        bootToBaseline[nBootToBaseline] = tm;
        ++nBootToBaseline;
    }

    public static TargetMethod[] bootToBaselineCallers() {
        return bootToBaseline;
    }

    public static void bootToBaselineDo(final TargetMethod.Closure closure) {
        for (int i = 0; i < nBootToBaseline; i++) {
            if (!closure.doTargetMethod(bootToBaseline[i])) {
                return;
            }
        }
    }

    /**
     * Allocates memory for the code-related arrays of a given target method
     * and {@linkplain TargetMethod#setCodeArrays(byte[], Pointer, byte[], Object[]) initializes} them.
     *
     * @param targetBundleLayout describes the layout of the arrays in the allocated space
     * @param targetMethod the target method for which the code-related arrays are allocated
     * @param inHeap specifies if the memory should be allocated in a code region or on the heap
     */
    synchronized void allocate(TargetBundleLayout targetBundleLayout, TargetMethod targetMethod, boolean inHeap, Lifespan lifespan) {
        final Size bundleSize = targetBundleLayout.bundleSize();
        int codeLength = targetBundleLayout.length(ArrayField.code);
        int scalarLiteralsLength = targetBundleLayout.length(ArrayField.scalarLiterals);
        int referenceLiteralsLength = targetBundleLayout.length(ArrayField.referenceLiterals);
        int trampolinesLength = targetBundleLayout.length(ArrayField.trampolines);

        final Size allocationSize;
        CodeRegion currentCodeRegion = null;

        allocationSize = bundleSize;
        Object allocationTraceDescription = Code.TraceCodeAllocation ? (targetMethod.classMethodActor() == null ? targetMethod.regionName() : targetMethod.classMethodActor()) : null;

        Pointer start;
        boolean mustReenableSafepoints = false;
        if (inHeap) {
            assert !isHosted();
            int byteArraySize = allocationSize.minus(Layout.byteArrayLayout().headerSize()).toInt();
            byte[] buf = new byte[byteArraySize];

            // 'buf' must not move until it has been reformatted
            mustReenableSafepoints = !SafepointPoll.disable();

            start = Layout.originToCell(Reference.fromJava(buf).toOrigin());
        } else {
            if (!isHosted()) {
                // The allocation and initialization of objects in a code region must be atomic with respect to garbage collection.
                mustReenableSafepoints = !SafepointPoll.disable();
                Heap.disableAllocationForCurrentThread();
                if (lifespan == Lifespan.LONG) {
                    currentCodeRegion = runtimeOptCodeRegion;
                } else {
                    currentCodeRegion = runtimeBaselineCodeRegion;
                }
            } else {
                currentCodeRegion = Code.bootCodeRegion();
            }

            if (currentCodeRegion == runtimeBaselineCodeRegion && CodeCacheContentionFrequency > 0 && ++nAllocations % CodeCacheContentionFrequency == 0) {
                start = Pointer.zero();
            } else {
                start = currentCodeRegion.allocate(allocationSize, false);
            }

            // Allocation in the baseline code region may take another attempt upon contention, after compaction.
            if (start.isZero() && currentCodeRegion == runtimeBaselineCodeRegion) {
                CodeEviction.run();
                assert validateCodeCache();
                start = currentCodeRegion.allocate(allocationSize, false);
                if (CodeCacheContentionFrequency > 0 && CodeEviction.logging()) {
                    CodeEviction.codeEvictionLogger.logStats_Surviving(lastSurvivorSize, largestSurvivorSize);
                }
            }
        }

        traceChunkAllocation(allocationTraceDescription, allocationSize, start, inHeap);
        if (start.isZero()) {
            if (mustReenableSafepoints) {
                SafepointPoll.enable();
            }
            Heap.enableAllocationForCurrentThread();
            Log.print("Out of memory allocating in code region named " + currentCodeRegion.regionName());
            if (currentCodeRegion == runtimeBaselineCodeRegion) {
                Log.println(" - try larger value for " + runtimeBaselineCodeRegionSize.toString() + "<n>");
            } else if (currentCodeRegion == runtimeOptCodeRegion) {
                Log.println(" - try larger value for " + runtimeOptCodeRegionSize.toString() + "<n>");
            }
            MaxineVM.exit(11);
        }

        targetMethod.setStart(start);
        targetMethod.setSize(allocationSize);

        // Initialize the objects in the allocated space so that they appear as a set of contiguous
        // well-formed objects that can be traversed.
        byte[] code;
        byte[] trampolines = null;
        byte[] scalarLiterals = null;
        Object[] referenceLiterals = null;
        Pointer trampolineStart = Pointer.zero();
        if (MaxineVM.isHosted()) {
            code = new byte[codeLength];
            trampolines = trampolinesLength == 0 ? null : new byte[trampolinesLength];
            scalarLiterals = scalarLiteralsLength == 0 ? null : new byte[scalarLiteralsLength];
            referenceLiterals = referenceLiteralsLength == 0 ? null : new Object[referenceLiteralsLength];
        } else {
            final Pointer codeCell = targetBundleLayout.cell(start, ArrayField.code);
            code = (byte[]) Cell.plantArray(codeCell, ClassRegistry.BYTE_ARRAY.dynamicHub(), codeLength);
            if (trampolinesLength != 0) {
                final Pointer trampolinesCell = targetBundleLayout.cell(start, ArrayField.trampolines);
                trampolines = (byte[]) Cell.plantArray(trampolinesCell, ClassRegistry.BYTE_ARRAY.dynamicHub(), trampolinesLength);
            }
            if (scalarLiteralsLength != 0) {
                final Pointer scalarLiteralsCell = targetBundleLayout.cell(start, ArrayField.scalarLiterals);
                scalarLiterals = (byte[]) Cell.plantArray(scalarLiteralsCell, ClassRegistry.BYTE_ARRAY.dynamicHub(), scalarLiteralsLength);
            }
            if (referenceLiteralsLength != 0) {
                final Pointer referenceLiteralsCell = targetBundleLayout.cell(start, ArrayField.referenceLiterals);
                referenceLiterals = (Object[]) Cell.plantArray(referenceLiteralsCell, ClassActor.fromJava(Object[].class).dynamicHub(), referenceLiteralsLength);
            }
            if (Code.TraceCodeAllocation) {
                traceAllocation(targetBundleLayout, bundleSize, scalarLiteralsLength, referenceLiteralsLength, start, codeCell, trampolinesLength);
            }
        }

        final Pointer codeStart = targetBundleLayout.firstElementPointer(start, ArrayField.code);
        if (trampolinesLength != 0) {
            trampolineStart = targetBundleLayout.firstElementPointer(start, ArrayField.trampolines);
        }

        targetMethod.setCodeArrays(code, codeStart, trampolines, trampolineStart, scalarLiterals, referenceLiterals);
        if (currentCodeRegion == runtimeBaselineCodeRegion) {
            targetMethod.protect();
        }

        if (!MaxineVM.isHosted()) {
            // It is now safe again to perform operations that may block and/or trigger a garbage collection
            if (mustReenableSafepoints) {
                SafepointPoll.enable();
            }
            if (!inHeap) {
                Heap.enableAllocationForCurrentThread();
            }
        }

        if (currentCodeRegion != null) {
            currentCodeRegion.add(targetMethod);
        }
    }

    private void traceAllocation(TargetBundleLayout targetBundleLayout, Size bundleSize, int scalarLiteralsLength, int referenceLiteralsLength, Pointer start, Pointer codeCell, int trampolinesLength) {
        final boolean lockDisabledSafepoints = Log.lock();
        Log.printCurrentThread(false);
        Log.print(": Code arrays: code=[");
        Log.print(codeCell);
        Log.print(" - ");
        Log.print(targetBundleLayout.cellEnd(start, ArrayField.code));
        Log.print("], scalarLiterals=");
        if (scalarLiteralsLength > 0) {
            Log.print(targetBundleLayout.cell(start, ArrayField.scalarLiterals));
            Log.print(" - ");
            Log.print(targetBundleLayout.cellEnd(start, ArrayField.scalarLiterals));
            Log.print("], referenceLiterals=");
        } else {
            Log.print("0, referenceLiterals=");
        }
        if (referenceLiteralsLength > 0) {
            Log.print(targetBundleLayout.cell(start, ArrayField.referenceLiterals));
            Log.print(" - ");
            Log.print(targetBundleLayout.cellEnd(start, ArrayField.referenceLiterals));
            Log.print("], trampolines=");
        } else {
            Log.print("0, trampolines=");
        }
        if (trampolinesLength > 0) {
            Log.print(targetBundleLayout.cell(start, ArrayField.trampolines));
            Log.print(" - ");
            Log.print(targetBundleLayout.cellEnd(start, ArrayField.trampolines));
            Log.println("]");
        } else {
            Log.println(0);
        }
        Log.unlock(lockDisabledSafepoints);
    }

    private void traceChunkAllocation(Object purpose, Size size, Pointer cell, boolean inHeap) {
        if (!cell.isZero() && purpose != null) {
            final boolean lockDisabledSafepoints = Log.lock();
            Log.printCurrentThread(false);
            if (inHeap) {
                Log.print(": Allocated chunk in heap for ");
            } else {
                Log.print(": Allocated chunk in code cache for ");
            }
            if (purpose instanceof MethodActor) {
                Log.printMethod((MethodActor) purpose, false);
            } else {
                Log.print(purpose);
            }
            Log.print(" at ");
            Log.print(cell);
            Log.print(" [size ");
            Log.print(size.wordAligned().toInt());
            Log.print(", end=");
            Log.print(cell.plus(size.wordAligned()));
            Log.println(']');
            Log.unlock(lockDisabledSafepoints);
        }
    }

    /**
     * Looks up the code region in which the specified code pointer lies. This lookup includes
     * the boot code region.
     *
     * @param codePointer the code pointer
     * @return a reference to the code region that contains the specified code pointer, if one exists; {@code null} if
     *         the code pointer lies outside of all code regions
     */
    CodeRegion codePointerToCodeRegion(Address codePointer) {
        if (Code.bootCodeRegion().contains(codePointer)) {
            return Code.bootCodeRegion();
        }
        if (runtimeBaselineCodeRegion.contains(codePointer)) {
            return runtimeBaselineCodeRegion;
        }
        if (runtimeOptCodeRegion.contains(codePointer)) {
            return runtimeOptCodeRegion;
        }
        return null;
    }

    /**
     * Looks up the target method that contains the specified code pointer.
     *
     * @param codePointer the code pointer to lookup
     * @return the target method that contains the specified code pointer, if it exists; {@code null}
     * if no target method contains the specified code pointer
     */
    TargetMethod codePointerToTargetMethod(Address codePointer) {
        TargetMethod result = null;
        final CodeRegion codeRegion = codePointerToCodeRegion(codePointer);
        if (codeRegion != null) {
            result = codeRegion.find(codePointer);
        }
        return result;
    }

    /**
     * Visit the cells in all the code regions in this code manager.
     *
     * @param cellVisitor the visitor to call back for each cell in each region
     * @param includeBootCode specifies if the cells in the {@linkplain Code#bootCodeRegion() boot code region} should
     *            also be visited
     */
    void visitCells(CellVisitor cellVisitor, boolean includeBootCode) {
        if (includeBootCode) {
            visitAllIn(cellVisitor, Code.bootCodeRegion());
        }
        visitAllIn(cellVisitor, runtimeBaselineCodeRegion);
        visitAllIn(cellVisitor, runtimeOptCodeRegion);
    }

    void visitAllIn(CellVisitor v, CodeRegion cr) {
        Pointer firstCell = cr.gcstart().asPointer();
        Pointer cell = firstCell;
        if (Heap.verbose()) {
            Log.print("Visit Region: ");
            Log.print(cr.regionName());
            Log.println();
        }
        while (cell.lessThan(cr.getAllocationMark())) {
            cell = DebugHeap.checkDebugCellTag(firstCell, cell);
            cell = v.visitCell(cell);
        }
    }

    /**
     * Return size of runtime baseline code region.
     * @return size of runtime baseline code region
     */
    public Size getRuntimeBaselineCodeRegionSize() {
        return runtimeBaselineCodeRegionSize.getValue();
    }

    /**
     * Return size of runtime opt code region.
     * @return size of runtime opt code region
     */
    public Size getRuntimeOptCodeRegionSize() {
        return runtimeOptCodeRegionSize.getValue();
    }

    /**
     * By definition, short-lived methods go to the baseline code region.
     */
    public static boolean isShortlived(TargetMethod tm) {
        return runtimeBaselineCodeRegion.contains(tm.start());
    }

    /**
     * A collection of methods that support certain inspection services.
     * The public methods are to be called by all implementations when
     * the specified events occur.
     *
     */
    public static final class Inspect {

        /**
         * Announces that a code eviction is about to begin.  It does almost
         * nothing, but it must be called by managed code region implementations for
         * certain Inspector services to work.
         * <p>
         * This should be called after any preliminary steps that do not modify
         * the code region have been completed.
         *
         * @param codeRegion the region of VM code cache in which eviction is about to start
         */
        public static void notifyEvictionStarted(CodeRegion codeRegion) {
            InspectableCodeInfo.notifyEvictionStarted(codeRegion);
            inspectableCodeEvictionStarted();
        }

        /**
         * Announces that a code eviction has finished.  It does almost
         * nothing, but it must be called by managed code region implementations for
         * certain Inspector services to work.
         * <p>
         * This should be called as soon as possible after all changes have been
         * made, for example before any non-destructive verification..
         *
         * @param codeRegion the region of VM code cache in which eviction just completed
         */
        public static void notifyEvictionCompleted(CodeRegion codeRegion) {
            InspectableCodeInfo.notifyEvictionCompleted(codeRegion);
            inspectableCodeEvictionCompleted();
        }

        private Inspect() {
        }

        /**
         * An empty method whose purpose is to be interrupted by the Inspector
         * at the beginning of a code eviction.
         * <p>
         * This particular method is intended for  use by users of the Inspector, and
         * is distinct from a method used by the Inspector for internal use.
         * <p>
         * <strong>Important:</strong> The Inspector assumes that this method is loaded
         * and compiled in the boot image and that it will never be dynamically recompiled.
         */
        @INSPECTED
        @NEVER_INLINE
        private static void inspectableCodeEvictionStarted() {
        }

        /**
         * An empty method whose purpose is to be interrupted by the Inspector
         * at the completion of a code eviction.
         * <p>
         * This particular method is intended for  use by users of the Inspector, and
         * is distinct from a method used by the Inspector for internal use.
         * <p>
         * <strong>Important:</strong> The Inspector assumes that this method is loaded
         * and compiled in the boot image and that it will never be dynamically recompiled.
         */
        @INSPECTED
        @NEVER_INLINE
        private static void inspectableCodeEvictionCompleted() {
        }
    }

}
